package db.queries.core;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import core.exceptions.DBAccessException;
import core.objects.ObjectWithID;
import db.DBHandler;
import db.IDBAccess;
import db.dbTables.AbstractCrossDBTable;
import db.dbTables.AbstractDBTable;
import db.dbTables.DbColumn;

public abstract class BaseObjectQueries<T extends ObjectWithID> implements IDBAccess<T> {

	protected abstract AbstractDBTable getTable();
	protected abstract DbColumn getIDColumn();
	protected abstract DbColumn getNameColumn();
	
	protected DBAccessException handleSQLException(SQLException e) {
		return new DBAccessException("Error: unknown error", e);
	}

	@Override
	public synchronized boolean exists(String name) throws DBAccessException {
		ResultSet rs = null;
		try {
			rs = DBHandler.executeQuery(
					QueriesEnvironment.selectCountQuery(getNameColumn(), name));
		    rs.first();
		    return (rs.getInt(1) != 0);
		} catch (SQLException e) {
			throw handleSQLException(e);
		} finally {
			try {
				if (rs != null)
					rs.close();
			} catch (SQLException e) {
				throw handleSQLException(e);
			}
		}

	}
	
	@Override
	public boolean exists(String name, int id, ObjectWithID obj) throws DBAccessException {
		//only objects holding objects (representing cross tables)
		//can support this kind of select!
		throw new UnsupportedOperationException();
	}
	
	@Override
	public boolean exists(int idToSearch, int id, ObjectWithID obj) throws DBAccessException {
		//only objects holding objects (representing cross tables)
		//can support this kind of select!
		throw new UnsupportedOperationException();
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public synchronized List<T> select(int id) throws DBAccessException {
		try {
			AbstractDBTable table = getTable();
			if (table instanceof AbstractCrossDBTable) {
				return (List<T>)DBHandler.convertResultSetToBaseObjectList(
						DBHandler.executeQuery(
								QueriesEnvironment.selectCrossQuery(
										getIDColumn().getName() + " = ? ",
										new Object[] { id },
										(AbstractCrossDBTable)table, ((AbstractCrossDBTable)table).getTable1())),
						((AbstractCrossDBTable)table).getTable1());
			} else {
				return (List<T>)DBHandler.convertResultSetToBaseObjectList(
						DBHandler.executeQuery(
								QueriesEnvironment.selectQuery(
										getIDColumn().getName() + " = ? ",
										new Object[] { id },
										table.getDbColumns().toArray(new DbColumn[0]))),
						table);
			}
		} catch (SQLException e) {
			throw handleSQLException(e);
		}
	}
	
	@Override
	public synchronized List<T> select(int id, ObjectWithID obj) throws DBAccessException {
		//only objects holding objects (representing cross tables)
		//can support this kind of select!
		throw new UnsupportedOperationException();
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public synchronized List<T> select(String name) throws DBAccessException {
		try {
			//TODO: handle SQL injection!!
			AbstractDBTable table = getTable();
			if (table instanceof AbstractCrossDBTable) {
				return (List<T>)DBHandler.convertResultSetToBaseObjectList(
						DBHandler.executeQuery(
								QueriesEnvironment.selectCrossQuery(
										getNameColumn().getName() + " = ? ",
										new Object[] { name },
										(AbstractCrossDBTable)table, ((AbstractCrossDBTable)table).getTable1())),
						((AbstractCrossDBTable)table).getTable1());
			} else {
				return (List<T>)DBHandler.convertResultSetToBaseObjectList(
						DBHandler.executeQuery(
								QueriesEnvironment.selectQuery(
										getNameColumn().getName() + " = ? ",
										new Object[] { name },
										table.getDbColumns().toArray(new DbColumn[0]))),
						table);
			}
		} catch (SQLException e) {
			throw handleSQLException(e);
		}
	}
	
	@Override
	public synchronized List<T> selectContains(String name) throws DBAccessException {
		return selectContains(name, false);
	}
	@SuppressWarnings("unchecked")
	@Override
	public synchronized List<T> selectContains(String name, boolean limitSearch) throws DBAccessException {
		try {
			//TODO: handle SQL injection!!
			AbstractDBTable table = getTable();
			if (table instanceof AbstractCrossDBTable) {
				return (List<T>)DBHandler.convertResultSetToBaseObjectList(
						DBHandler.executeQuery(
								QueriesEnvironment.selectCrossQuery(
										getNameColumn().getName() + " LIKE ? ",
										new Object[] { "%" + name + "%" },
										(AbstractCrossDBTable)table, ((AbstractCrossDBTable)table).getTable1(),
										limitSearch)),
						((AbstractCrossDBTable)table).getTable1());
			} else {
				return (List<T>)DBHandler.convertResultSetToBaseObjectList(
						DBHandler.executeQuery(
								QueriesEnvironment.selectQuery(
										getNameColumn().getName() + " LIKE ? ",
										new Object[] { "%" + name + "%" },
										getTable().getDbColumns().toArray(new DbColumn[0]),
										limitSearch)),
						getTable());
			}
		} catch (SQLException e) {
			throw handleSQLException(e);
		}
	}
	
	@Override
	public List<T> select(String name, ObjectWithID obj) throws DBAccessException {
		//only objects holding objects (representing cross tables)
		//can support this kind of select!
		throw new UnsupportedOperationException();
	}

	@Override
	public List<T> selectContains(String name, ObjectWithID obj) throws DBAccessException {
		//only objects holding objects (representing cross tables)
		//can support this kind of select!
		throw new UnsupportedOperationException();
	}

	@Override
	public List<T> selectContains(String name, ObjectWithID obj, boolean limitSearch) throws DBAccessException {
		//only objects holding objects (representing cross tables)
		//can support this kind of select!
		throw new UnsupportedOperationException();
	}

	@Override
	public int selectCount(int id, ObjectWithID obj) throws DBAccessException {
		//only very specific objects can support this operation (and all
		//are flat, ObjectWithID, not BaseObject)
		throw new UnsupportedOperationException();			
	}

	@Override
	public float selectAvg(int id) throws DBAccessException {
		//only very specific objects can support this operation (and all
		//are flat, ObjectWithID, not BaseObject)
		throw new UnsupportedOperationException();
	}
	
	@SuppressWarnings("unchecked")
	@Override
	public synchronized List<T> selectAll() throws DBAccessException {
		try {
			return (List<T>) DBHandler.convertResultSetToBaseObjectList(
					DBHandler.executeQuery(
							QueriesEnvironment.selectQuery(null, null,
									getTable().getDbColumns().toArray(new DbColumn[0]))),
					getTable());
		} catch (SQLException e) {
			throw handleSQLException(e);
		}		
	}
	
	@Override
	public List<? extends ObjectWithID> selectAll(int id, ObjectWithID obj) throws DBAccessException {
		//only objects holding objects (representing cross tables)
		//can support this kind of select!
		throw new UnsupportedOperationException();
	}

	@Override
	public void insert(T obj) throws DBAccessException {
		ArrayList<T> objs = new ArrayList<T>();
		objs.add(obj);
		insertAll(objs);
	}
	
	@Override
	public void insert(T obj, ObjectWithID base) throws DBAccessException {
		ArrayList<ObjectWithID> objs = new ArrayList<ObjectWithID>();
		objs.add(base);
		insertAll(obj, objs);
	}
	
	@Override
	public void insertAll(Collection<T> objs) throws DBAccessException {
		try {
			DBHandler.createAndExecuteBatchUpdate(getTable(), objs);
		} catch (SQLException e) {
			throw handleSQLException(e);
		}
	}

	@Override
	public void insertAll(T obj, Collection<? extends ObjectWithID> objs)
			throws DBAccessException {
		try {
			DBHandler.createAndExecuteCrossBatchUpdate(
					getTable(), obj.getID(), objs);
		} catch (SQLException e) {
			throw handleSQLException(e);
		}
	}
	
	@Override
	public void insertAll(Collection<? extends ObjectWithID> objs,
			ObjectWithID filter) throws DBAccessException {
		throw new UnsupportedOperationException();
	}
	
	@Override
	public void update(T obj) throws DBAccessException {
		ArrayList<T> objs = new ArrayList<T>();
		objs.add(obj);
		update(objs);
	}
	
	@Override
	public void update(Collection<T> objs) throws DBAccessException {
		try {
			DBHandler.executeUpdateBatch(QueriesEnvironment.doUpdate(objs, getTable()));
		} catch (SQLException e) {
			throw handleSQLException(e);
		}
	}
	
	@Override
	public void delete(T obj) throws DBAccessException {
		ArrayList<T> objs = new ArrayList<T>();
		objs.add(obj);
		delete(objs);
	}
		
	@Override
	public void delete(Collection<T> objs) throws DBAccessException {
		try {
			DBHandler.executeUpdateBatch(QueriesEnvironment.doDelete(objs, getTable()));
		} catch (SQLException e) {
			throw handleSQLException(e);
		}
	}
	
	@Override
	public void delete(T obj1, ObjectWithID obj2) throws DBAccessException {
		//only objects holding objects (representing cross tables)
		//can support this kind of select!
		throw new UnsupportedOperationException();
	}
	
	@Override
	public void delete(T obj, Collection<? extends ObjectWithID> objs)
			throws DBAccessException {
		//only objects holding objects (representing cross tables)
		//can support this kind of select!
		throw new UnsupportedOperationException();
	}
	
}
